﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace Light.Utility.Security
{
    /// <summary>
    /// RSA加、解密
    /// </summary>
    public sealed class RSAParser
    {
        private static readonly Encoding encoding = Encoding.UTF8;//Encoding.GetEncoding("gb2312");

        #region LoadRsaPublicKey

        /// <summary>
        /// 从文件中加载RSA公钥(1024 or 2048)
        /// </summary>
        /// <param name="file">存储RSA公钥的文件</param>
        /// <returns></returns>
        public static RSAParameters LoadRsaPublicKeyFile(string file)
        {
            if (!File.Exists(file))
            {
                throw new FileNotFoundException("RSA Public File not found", file);
            }

            string pemFileConent = File.ReadAllText(file); // 从文件读取内容
            RSAParameters publicKey = LoadRsaPublicKeyConent(pemFileConent);

            return publicKey;
        }

        /// <summary>
        /// 从文本内容中加载RSA公钥(1024 or 2048)
        ///     移除前导"-----BEGIN PUBLIC KEY-----", 以及结尾"-----END PUBLIC KEY-----"
        /// </summary>
        /// <param name="pemFileConent">pem公钥内容</param>
        /// <returns>转换得到的RSAParamenters</returns>
        public static RSAParameters LoadRsaPublicKeyConent(string pemFileConent)
        {
            return LoadRsaPublicKeyConent(pemFileConent, "-----BEGIN PUBLIC KEY-----", "-----END PUBLIC KEY-----");
        }

        /// <summary>
        /// 从文本内容中加载RSA公钥(1024 or 2048)
        /// </summary>
        /// <param name="pemFileConent">RSA公钥内容</param>
        /// <param name="trimStart">需要移除的前导内容, 默认移除"-----BEGIN PUBLIC KEY-----"</param>
        /// <param name="trimEnd">需要移除的结尾内容, 默认移除"-----END PUBLIC KEY-----"</param>
        /// <returns>转换得到的RSAParamenters</returns>
        public static RSAParameters LoadRsaPublicKeyConent(string pemFileConent, string trimStart, string trimEnd)
        {
            if (string.IsNullOrEmpty(pemFileConent))
            {
                throw new ArgumentNullException("pemFileConent", "This arg cann't be empty.");
            }

            if (trimStart == null || trimStart.Length == 0)
                trimStart = "-----BEGIN PUBLIC KEY-----";
            if (trimEnd == null || trimEnd.Length == 0)
                trimStart = "-----END PUBLIC KEY-----";

            pemFileConent = pemFileConent.Replace(trimStart, "").Replace(trimEnd, "").Replace("\n", "").Replace("\r", "");
            byte[] keyData = Convert.FromBase64String(pemFileConent);
            bool keySize1024 = (keyData.Length == 162);
            bool keySize2048 = (keyData.Length == 294);
            if (!(keySize1024 || keySize2048))
            {
                throw new ArgumentException("RSA Public file content is incorrect, Only support the key size is 1024 or 2048");
            }

            byte[] pemModulus = (keySize1024 ? new byte[128] : new byte[256]);
            byte[] pemPublicExponent = new byte[3];
            Array.Copy(keyData, (keySize1024 ? 29 : 33), pemModulus, 0, (keySize1024 ? 128 : 256));
            Array.Copy(keyData, (keySize1024 ? 159 : 291), pemPublicExponent, 0, 3);
            RSAParameters para = new RSAParameters();
            para.Modulus = pemModulus;
            para.Exponent = pemPublicExponent;
            return para;
        }

        #endregion

        #region LoadRsaPrivateKey

        /// <summary>
        /// 从文件中加载RSA私钥(1024 or 2048)
        /// </summary>
        /// <param name="file">存储RSA私钥的文件</param>
        /// <returns></returns>
        public static RSAParameters LoadRsaPrivateKeyFile(string file)
        {
            if (!File.Exists(file))
            {
                throw new FileNotFoundException("RSA Private File not found", file);
            }

            string pemFileConent = File.ReadAllText(file); // 从文件读取内容
            RSAParameters publicKey = LoadRsaPrivateKeyConent(pemFileConent);

            return publicKey;
        }

        /// <summary>
        /// 从文本内容中加载RSA私钥(1024 or 2048)
        ///     过滤前导"-----BEGIN RSA PRIVATE KEY-----",以及结尾"-----END RSA PRIVATE KEY-----"
        /// </summary>
        /// <param name="pemFileConent">RSA私钥内容</param>
        /// <returns>转换得到的RSAParamenters</returns>
        public static RSAParameters LoadRsaPrivateKeyConent(string pemFileConent)
        {
            return LoadRsaPrivateKeyConent(pemFileConent, "-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----");
        }

        /// <summary>
        /// 从文本内容中加载RSA私钥(1024 or 2048)
        /// </summary>
        /// <param name="pemFileConent">RSA私钥内容</param>
        /// <param name="trimStart">需要移除的前导内容, 为空时默认移除"-----BEGIN RSA PRIVATE KEY-----"</param>
        /// <param name="trimEnd">需要移除的结尾内容, 为空时默认移除"-----END RSA PRIVATE KEY-----"</param>
        /// <returns>转换得到的RSAParamenters</returns>
        public static RSAParameters LoadRsaPrivateKeyConent(string pemFileConent, string trimStart, string trimEnd)
        {
            if (string.IsNullOrEmpty(pemFileConent))
            {
                throw new ArgumentNullException("pemFileConent", "This arg cann't be empty.");
            }

            if (trimStart == null || trimStart.Length == 0)
                trimStart = "-----BEGIN RSA PRIVATE KEY-----";
            if (trimEnd == null || trimEnd.Length == 0)
                trimStart = "-----END RSA PRIVATE KEY-----";


            pemFileConent = pemFileConent.Replace(trimStart, "").Replace(trimEnd, "").Replace("\n", "").Replace("\r", "");
            byte[] keyData = Convert.FromBase64String(pemFileConent);

            bool keySize1024 = (keyData.Length == 609 || keyData.Length == 610);
            bool keySize2048 = (keyData.Length == 1190 || keyData.Length == 1192);

            if (!(keySize1024 || keySize2048))
            {
                throw new ArgumentException("RSA Private file content is incorrect, Only support the key size is 1024 or 2048");
            }

            int index = (keySize1024 ? 11 : 12);
            byte[] pemModulus = (keySize1024 ? new byte[128] : new byte[256]);
            Array.Copy(keyData, index, pemModulus, 0, pemModulus.Length);

            index += pemModulus.Length;
            index += 2;
            byte[] pemPublicExponent = new byte[3];
            Array.Copy(keyData, index, pemPublicExponent, 0, 3);

            index += 3;
            index += 4;
            if ((int)keyData[index] == 0)
            {
                index++;
            }
            byte[] pemPrivateExponent = (keySize1024 ? new byte[128] : new byte[256]);
            Array.Copy(keyData, index, pemPrivateExponent, 0, pemPrivateExponent.Length);

            index += pemPrivateExponent.Length;
            index += (keySize1024 ? ((int)keyData[index + 1] == 64 ? 2 : 3) : ((int)keyData[index + 2] == 128 ? 3 : 4));
            byte[] pemPrime1 = (keySize1024 ? new byte[64] : new byte[128]);
            Array.Copy(keyData, index, pemPrime1, 0, pemPrime1.Length);

            index += pemPrime1.Length;
            index += (keySize1024 ? ((int)keyData[index + 1] == 64 ? 2 : 3) : ((int)keyData[index + 2] == 128 ? 3 : 4));
            byte[] pemPrime2 = (keySize1024 ? new byte[64] : new byte[128]);
            Array.Copy(keyData, index, pemPrime2, 0, pemPrime2.Length);

            index += pemPrime2.Length;
            index += (keySize1024 ? ((int)keyData[index + 1] == 64 ? 2 : 3) : ((int)keyData[index + 2] == 128 ? 3 : 4));
            byte[] pemExponent1 = (keySize1024 ? new byte[64] : new byte[128]);
            Array.Copy(keyData, index, pemExponent1, 0, pemExponent1.Length);

            index += pemExponent1.Length;
            index += (keySize1024 ? ((int)keyData[index + 1] == 64 ? 2 : 3) : ((int)keyData[index + 2] == 128 ? 3 : 4));
            byte[] pemExponent2 = (keySize1024 ? new byte[64] : new byte[128]);
            Array.Copy(keyData, index, pemExponent2, 0, pemExponent2.Length);

            index += pemExponent2.Length;
            index += (keySize1024 ? ((int)keyData[index + 1] == 64 ? 2 : 3) : ((int)keyData[index + 2] == 128 ? 3 : 4));
            byte[] pemCoefficient = (keySize1024 ? new byte[64] : new byte[128]);
            Array.Copy(keyData, index, pemCoefficient, 0, pemCoefficient.Length);

            RSAParameters para = new RSAParameters();
            para.Modulus = pemModulus;
            para.Exponent = pemPublicExponent;
            para.D = pemPrivateExponent;
            para.P = pemPrime1;
            para.Q = pemPrime2;
            para.DP = pemExponent1;
            para.DQ = pemExponent2;
            para.InverseQ = pemCoefficient;
            return para;
        }

        #endregion

        #region Encrypt (加密)

        /// <summary>
        /// 加密字符串。返回加密后的密文
        /// </summary>
        /// <param name="publicKeyFile">RSA公钥文件路径</param>
        /// <param name="text">需要加密的字符串(明文)</param>
        /// <returns>返回加密后的密文</returns>
        public static string Encrypt(string publicKeyFile, string text)
        {
            RSAParameters publicKey = LoadRsaPublicKeyFile(publicKeyFile);
            return Encrypt(publicKey, text);
        }
        /// <summary>
        /// 加密字符串。返回加密后的密文
        /// </summary>
        /// <param name="publicKey">RSA公钥</param>
        /// <param name="text">需要加密的字符串(明文)</param>
        /// <returns>返回加密后的密文</returns>
        public static string Encrypt(RSAParameters publicKey, string text)
        {
            string src = null;
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(publicKey);

            int returnSize = 0;
            int size = 86;// rsa.KeySize / 8 - 11;

            byte[] bytes = encoding.GetBytes(text);
            int length = bytes != null ? bytes.Length : 0;

            if (length > 0)
            {
                int nextIndex = 0;
                do
                {
                    returnSize = Math.Min(length - nextIndex, size);
                    byte[] block = new byte[returnSize];
                    Array.Copy(bytes, nextIndex, block, 0, returnSize);

                    if (block != null)
                    {
                        byte[] encryptedBytes = rsa.Encrypt(block, true);
                        src += Convert.ToBase64String(encryptedBytes);
                    }

                    nextIndex += returnSize;
                }
                while (nextIndex < length && returnSize != 0);
            }

            return src;
        }


        /// <summary>
        /// 加密文件。返回加密后的密文
        /// </summary>
        /// <param name="publicKeyFile">RSA公钥文件路径</param>
        /// <param name="textFile">需要加密的文件路径(明文文件)</param>
        /// <returns>返回加密后的密文</returns>
        public static string EncryptFile(string publicKeyFile, string textFile)
        {
            RSAParameters publicKey = LoadRsaPublicKeyFile(publicKeyFile);
            return EncryptFile(publicKey, textFile);
        }
        /// <summary>
        /// 加密文件。返回加密后的密文
        /// </summary>
        /// <param name="publicKey">RSA公钥</param>
        /// <param name="textFile">需要加密的文件路径(明文文件)</param>
        /// <returns>返回加密后的密文</returns>
        public static string EncryptFile(RSAParameters publicKey, string textFile)
        {
            string src = null;
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(publicKey);

            if (!File.Exists(textFile))
                throw new FileNotFoundException("text File not found", textFile);

            using (FileStream sr = new FileStream(textFile, FileMode.Open))
            {
                int returnSize = 0;
                int size = 86; //rsa.KeySize / 8 - 11;
                do
                {
                    byte[] bytes = new byte[size];
                    returnSize = sr.Read(bytes, 0, size);

                    byte[] encryptedBytes = rsa.Encrypt(bytes, true);
                    src += Convert.ToBase64String(encryptedBytes);
                }
                while (returnSize != 0);
            }

            return src;
        }


        /// <summary>
        /// 加密文件。
        /// </summary>
        /// <param name="publicKeyFile">RSA公钥文件路径</param>
        /// <param name="textFile">需要加密的文件路径(明文文件)</param>
        /// <param name="srcFile">用于保存密文的文件路径(密文文件)</param>
        public static void EncryptFile(string publicKeyFile, string textFile, string srcFile)
        {
            RSAParameters publicKey = LoadRsaPublicKeyFile(publicKeyFile);
            EncryptFile(publicKey, textFile, srcFile);
        }
        /// <summary>
        /// 加密文件。
        /// </summary>
        /// <param name="publicKey">RSA公钥</param>
        /// <param name="textFile">需要加密的文件路径(明文文件)</param>
        /// <param name="srcFile">用于保存密文的文件路径(密文文件)</param>
        public static void EncryptFile(RSAParameters publicKey, string textFile, string srcFile)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(publicKey);

            if (!File.Exists(textFile))
                throw new FileNotFoundException("text File not found", textFile);

            if (string.IsNullOrEmpty(srcFile))
                throw new ArgumentException("src File is empty", "srcFile");

            int length = Math.Max(srcFile.LastIndexOf('\\'), srcFile.LastIndexOf('/'));
            if (length > 0)
            {
                if (!Directory.Exists(srcFile.Substring(0, length)))
                    throw new DirectoryNotFoundException("src File directory not found");
            }

            if (!File.Exists(srcFile))
                File.CreateText(srcFile).Close();

            using (FileStream sr = new FileStream(textFile, FileMode.Open))
            {
                using (StreamWriter sw = new StreamWriter(srcFile, false, encoding))
                {
                    int returnSize = 0;
                    int size = 86; //rsa.KeySize / 8 - 11;
                    do
                    {
                        byte[] bytes = new byte[size];
                        returnSize = sr.Read(bytes, 0, size);

                        byte[] encryptedBytes = rsa.Encrypt(bytes, true);
                        sw.Write(Convert.ToBase64String(encryptedBytes));
                    }
                    while (returnSize != 0);

                    sw.Flush();
                }
            }
        }

        #endregion

        #region Decrypt (解密)

        /// <summary>
        /// 解密字符串。返回解密后的明文
        /// </summary>
        /// <param name="privateKeyFile">RSA私钥文件路径</param>
        /// <param name="src">需要解密的字符串(密文)</param>
        /// <returns>返回解密后的明文</returns>
        public static string Decrypt(string privateKeyFile, string src)
        {
            RSAParameters privateKey = LoadRsaPrivateKeyFile(privateKeyFile);
            return Decrypt(privateKey, src);
        }
        /// <summary>
        /// 解密字符串。返回解密后的明文
        /// </summary>
        /// <param name="privateKey">RSA私钥</param>
        /// <param name="src">需要解密的字符串(密文)</param>
        /// <returns>返回解密后的明文</returns>
        public static string Decrypt(RSAParameters privateKey, string src)
        {
            string outString = string.Empty;
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(privateKey);

            if (src != null)
            {
                foreach (string block in src.Split('='))
                {
                    if (!string.IsNullOrEmpty(block.Trim()))
                    {
                        byte[] bytes = Convert.FromBase64String(block.Trim() + "=");
                        byte[] decryptBytes = rsa.Decrypt(bytes, true);
                        outString += encoding.GetString(decryptBytes);
                    }
                }
            }

            return outString;
        }


        /// <summary>
        /// 解密字符串。返回解密后的明文
        /// </summary>
        /// <param name="privateKeyFile">RSA私钥文件路径</param>
        /// <param name="srcFile">需要解密的文件路径(密文文件)</param>
        /// <returns>返回解密后的明文</returns>
        public static byte[] DecryptFile(string privateKeyFile, string srcFile)
        {
            RSAParameters privateKey = LoadRsaPrivateKeyFile(privateKeyFile);
            return DecryptFile(privateKey, srcFile);
        }
        /// <summary>
        /// 解密字符串。返回解密后的明文
        /// </summary>
        /// <param name="privateKey">RSA私钥</param>
        /// <param name="srcFile">需要解密的文件路径(密文文件)</param>
        /// <returns>返回解密后的明文</returns>
        public static byte[] DecryptFile(RSAParameters privateKey, string srcFile)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(privateKey);

            if (!File.Exists(srcFile))
            {
                throw new FileNotFoundException("src File not found", srcFile);
            }

            List<byte> output = new List<byte>();
            string src = File.ReadAllText(srcFile);
            foreach (string block in src.Split('='))
            {
                if (!string.IsNullOrEmpty(block.Trim()))
                {
                    byte[] bytes = Convert.FromBase64String(block.Trim() + "=");
                    byte[] decryptBytes = rsa.Decrypt(bytes, true);
                    output.AddRange(decryptBytes);
                }
            }

            return output.ToArray();
        }


        /// <summary>
        /// 解密字符串。
        /// </summary>
        /// <param name="privateKey">RSA私钥文件路径</param>
        /// <param name="srcFile">需要解密的文件路径(密文文件)</param>
        /// <param name="textFile">用于保存加密原文的文件路径(明文文件)</param>
        public static void DecryptFile(string privateKeyFile, string srcFile, string textFile)
        {
            RSAParameters privateKey = LoadRsaPrivateKeyFile(privateKeyFile);
            DecryptFile(privateKey, srcFile, textFile);
        }
        /// <summary>
        /// 解密字符串。返回解密后的明文
        /// </summary>
        /// <param name="privateKey">RSA私钥</param>
        /// <param name="srcFile">需要解密的文件路径(密文文件)</param>
        /// <param name="textFile">用于保存加密原文的文件路径(明文文件)</param>
        public static void DecryptFile(RSAParameters privateKey, string srcFile, string textFile)
        {
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.ImportParameters(privateKey);

            if (!File.Exists(srcFile))
                throw new FileNotFoundException("src File not found", srcFile);

            if (string.IsNullOrEmpty(textFile))
                throw new ArgumentException("text File is empty", "textFile");

            int length = Math.Max(textFile.LastIndexOf('\\'), textFile.LastIndexOf('/'));
            if (length > 0)
            {
                if (!Directory.Exists(textFile.Substring(0, length)))
                    throw new DirectoryNotFoundException("text File directory not found");
            }

            if (!File.Exists(textFile))
                File.CreateText(textFile).Close();

            string src = File.ReadAllText(srcFile);
            using (FileStream sw = new FileStream(textFile, FileMode.OpenOrCreate, FileAccess.Write))
            {
                foreach (string block in src.Split('='))
                {
                    if (!string.IsNullOrEmpty(block.Trim()))
                    {
                        byte[] bytes = Convert.FromBase64String(block.Trim() + "=");
                        byte[] decryptBytes = rsa.Decrypt(bytes, true);
                        sw.Write(decryptBytes, 0, decryptBytes.Length);
                    }
                }

                sw.Flush();
            }
        }

        #endregion
    }
}